package differences;

import differences.interfaces.Core;
import hips.Partition;
import hips.gui.ProgressBarFrame;
import hips.gui.parameters.ParameterFrame;
import hips.gui.parameters.ParameterListener;
import hips.images.ImagePartitionable;
import hips.images.rgb.ImageRGB;
import hips.pixel.PixelValue;
import hips.region.NewRegionEvent;
import hips.region.NewRegionListener;
import hips.region.Region;
import ij.IJ;
import ij.ImagePlus;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.io.File;
import java.util.LinkedList;

public class Main extends Core implements ParameterListener, NewRegionListener, MouseMotionListener {

    int imagesOpened;
    Partition youngest;
    Partition oldest;
    ProgressBarFrame progress;
    long completed;
    int[] pixels;
    int[] references;
    int[] lbl;
    int label;
    Region common;
    ImageRGB output;

    public Main() {
        gui = new MainFrame();
        gui.setCore(this);
        imagesOpened = 0;
        common = null;
        gui.allowGetYoungestImage();
        gui.allowGetOldestImage();
        gui.dontAllowCustomizeParameters();
        gui.dontAllowGetDifferences();
    }

    public void customizeParameters() {
        ImagePartitionable img = youngest.getImage();
        if (img == null) {
            img = oldest.getImage();
        }
        ParameterFrame p = new ParameterFrame(img);
        p.addParameterListener(this);
        gui.disable();
    }

    public void ParametersOk(PixelValue alpha, PixelValue omega, float cindex) {
        youngest.setAlpha(alpha);
        youngest.setOmega(omega);
        youngest.setCindex(cindex);
        oldest.setAlpha(alpha);
        oldest.setOmega(omega);
        oldest.setCindex(cindex);
        gui.enable();
    }

    public void ParametersCancelled() {
        gui.enable();
    }

    public void getYoungestImage(File file) {
        ImagePartitionable img;
        if (imagesOpened == 0) {
            img = checkFirstImage(new ImagePlus(file.getAbsolutePath()));
            if (img != null) {
                imagesOpened++;
                gui.dontAllowGetYoungestImage();
            }
        } else {
            img = checkSecondImage(new ImagePlus(file.getAbsolutePath()), oldest.getImage());
            if (img != null) {
                imagesOpened++;
                gui.dontAllowGetOldestImage();
                gui.allowGetDifferences();
                gui.allowCustomizeParameters();
            }
        }
        if (img != null) {
            img.getImagePlus().show();
            youngest = img.newPartition();

        }
    }

    public void getOldestImage(File file) {
        ImagePartitionable img;
        if (imagesOpened == 0) {
            img = checkFirstImage(new ImagePlus(file.getAbsolutePath()));
            if (img != null) {
                imagesOpened++;
                gui.dontAllowGetOldestImage();
            }
        } else {
            img = checkSecondImage(new ImagePlus(file.getAbsolutePath()), youngest.getImage());
            if (img != null) {
                imagesOpened++;
                gui.dontAllowGetOldestImage();
                gui.allowGetDifferences();
                gui.allowCustomizeParameters();
            }
        }
        if (img != null) {
            img.getImagePlus().show();
            oldest = img.newPartition();
        }
    }

    public void getDifferences() {
        gui.dontAllowCustomizeParameters();
        youngest.addNewRegionEventListener(this);
        oldest.addNewRegionEventListener(this);
        completed = 0;
        progress = new ProgressBarFrame();
        progress.setText("creating partitions for the images");
        Thread t = new Thread(new Runnable() {

            public void run() {
                youngest.makeRegions();
                oldest.makeRegions();
                paintDifferences(oldest, calculateDifferences(oldest, youngest));
            }
        });
        t.start();
    }

    public void newRegionCreated(NewRegionEvent e) {
        completed += e.getRegion().getSize();
        progress.setPercentage(((float) completed) / ((float) (youngest.getImage().getSize() * 2)));
        if (completed == (youngest.getImage().getSize() * 2)) {
            progress.dispose();
        }
    }

    private float[] calculateDifferences(Partition a, Partition b) {

        pixels = new int[youngest.getImage().getSize()];
        references = new int[youngest.getImage().getSize()];
        lbl = new int[youngest.getImage().getSize()];
        float[] diff = new float[youngest.getImage().getSize()];

        // diff y references podrian ser de un tamaño menor

        int base = 0;
        label = 0;

        LinkedList<Region> regions = new LinkedList<Region>();
        for (int i = 0; i < a.getRegionSize(); i++) {
            Region r = a.getRegionByLabel(i);
            for (int j = 0; j < r.getSize(); j++) {
                lbl[r.getPixelPosition(j)] = Integer.MAX_VALUE;
            }

            for (int j = 0; j < r.getSize(); j++) {
                if (lbl[r.getPixelPosition(j)] == Integer.MAX_VALUE) {
                    Region r2 = b.getRegionByPixel(r.getPixelPosition(j));

                    int size = 0;
                    for (int k = 0; k < r2.getSize(); k++) {
                        if (lbl[r2.getPixelPosition(k)] == Integer.MAX_VALUE) {
                            lbl[r2.getPixelPosition(k)] = label;
                            pixels[base + size] = r2.getPixelPosition(k);
                            size++;
                        }
                    }
                    references[label] = base;
                    base += size;


                    float mult = size * (r2.getSize() - size) * (r.getSize() - size);

                    float div = r2.getSize() + r.getSize() - size;
                    float div3 = div / 3;

                    float result;

                    if (div % 3 == 0) {
                        result = mult / (div3 * div3 * div3);
                    } else if (div % 3 == 1) {
                        result = mult / (div3 * div3 * (div3 + 1));
                    } else {
                        result = mult / (div3 * (div3 + 1) * (div3 + 1));
                    }

                    float log1 = (float) (Math.log10(r.getSize()) + 1);
                    float log2 = (float) (Math.log10(r2.getSize()) + 1);

                    float maxLog = (log1 + log2) / 2;

                    diff[label] = result * (log1 * log2) / (maxLog * maxLog);
                    label++;

                }
            }
        }
        references[label] = base;
        return diff;
    }

    // 0.0 = verde, 1.0 = rojo
    private void paintDifferences(Partition p, float[] diff) {
        output = p.getImage().newImageRGB();
        output.getImagePlus().show();
        for (int i = 0; i < label; i++) {
            int size = references[i + 1] - references[i];
            Region r = new Region(pixels, references[i], size, 0);
            //System.out.println("diff: " + diff[i] + " - size: " + size);
            int R, G;
            if (diff[i] < 0.5f) {
                G = 255;
                R = (int) (511 * diff[i]);
            } else {
                R = 255;
                G = (int) (512 * (1f - diff[i]));
            }
            output.paintRegion(r, output.newPixelValue(ImageRGB.toRGB(R, G, 0)));
        }
        output.getImagePlus().getCanvas().addMouseMotionListener(this);
    }

    private float[] calculateDifferences2(Partition a, Partition b) {
        float[] diff = new float[a.getRegionSize()];
        LinkedList<Region> regions = new LinkedList<Region>();
        for (int i = 0; i < a.getRegionSize(); i++) {
            Region r = a.getRegionByLabel(i);
            for (int j = 0; j < r.getSize(); j++) {
                Region r2 = b.getRegionByPixel(r.getPixelPosition(j));
                for (Region r3 : regions) {
                    if (r2.sameAs(r3)) {
                        r2 = null;
                        break;
                    }
                }
                if (r2 != null) {
                    regions.add(r2);
                }
            }
            int size = 0;
            for (Region r3 : regions) {
                size += r3.getSize();
            }
            diff[i] = ((float) size) / ((float) r.getSize());
            //System.out.println(diff[i] + " - " + regions.size());
            regions.clear();
        }
        return diff;
    }

    // se toma 1.0 como el valor de diferencia nula
    private void paintDifferences2(Partition p, float[] diff) {
        float max = 0f;
        for (int i = 0; i < diff.length; i++) {
            if (diff[i] > max) {
                max = diff[i];
            }
        }
        ImageRGB out = p.getImage().newImageRGB();
        out.getImagePlus().show();

        max = max - 1f;
        if (max > 0f) {
            for (int i = 0; i < p.getRegionSize(); i++) {
                Region r = p.getRegionByLabel(i);
                float percent = (diff[i] - 1f) / (max);
                System.out.println("diff: " + diff[i] + " - percent: " + percent);
                int R, G;
                if (percent < 0.5f) {
                    G = 255;
                    R = (int) (511 * percent);
                } else {
                    R = 255;
                    G = (int) (512 * (1f - percent));
                }
                PixelValue pv = out.newPixelValue();
                out.paintRegion(r, out.newPixelValue(ImageRGB.toRGB(R, G, 0)));
            }
        }

    }

    public static ImagePartitionable checkFirstImage(ImagePlus imp) {
        if (imp == null) {
            IJ.error("ERROR", "The are no images open.");
            return null;
        }
        if (!imp.isProcessor()) {
            IJ.error("ERROR", "The ImagePlus opened is not valid.");
            return null;
        }
        if (imp.getStackSize() == 1) {
            if (imp.getType() == ImagePlus.COLOR_RGB) {
                return new hips.images.gray8.ImageGray8(imp);
            } else if (imp.getType() == ImagePlus.GRAY8) {
                return new hips.images.gray8.ImageGray8(imp);
            } else if (imp.getType() == ImagePlus.GRAY32) {
                return new hips.images.gray32.ImageGray32(imp);
            }
            IJ.error("ERROR", "The type image must be RGB or 8 bits gray or 32 bits gray.");
            return null;
        }
        if (imp.getStackSize() > 1) {
            if (imp.getType() == ImagePlus.GRAY8) {
                return new hips.images.gray8.ImageGray8(imp);
            } else if (imp.getType() == ImagePlus.GRAY32) {
                return new hips.images.gray32.ImageGray32(imp);
            }
            IJ.error("ERROR", "The stack type must be 8 bits gray or 32 bits gray.");
            return null;
        }
        IJ.error("ERROR", "Unknown error.");
        return null;
    }

    public static ImagePartitionable checkSecondImage(ImagePlus imp, ImagePartitionable first) {
        if (imp == null) {
            IJ.error("ERROR", "The are no images open.");
            return null;
        }
        if (!imp.isProcessor()) {
            IJ.error("ERROR", "The ImagePlus opened is not valid.");
            return null;
        }
        if (imp.getStackSize() != first.getImagePlus().getStackSize()) {
            IJ.error("ERROR", "The ImagePlus opened has not the same stack size than the first");
            return null;
        }
        if (imp.getType() != first.getImagePlus().getType()) {
            IJ.error("ERROR", "The ImagePlus opened has not the same type than the first");
            return null;
        }
        if (imp.getHeight() != first.getHeight()) {
            IJ.error("ERROR", "The ImagePlus opened has not the same height than the first");
            return null;
        }
        if (imp.getWidth() != first.getWidth()) {
            IJ.error("ERROR", "The ImagePlus opened has not the same width than the first");
            return null;
        }
        if (imp.getStackSize() == 1) {
            if (imp.getType() == ImagePlus.COLOR_RGB) {
                return new hips.images.gray8.ImageGray8(imp);
            } else if (imp.getType() == ImagePlus.GRAY8) {
                return new hips.images.gray8.ImageGray8(imp);
            } else if (imp.getType() == ImagePlus.GRAY32) {
                return new hips.images.gray32.ImageGray32(imp);
            }
            IJ.error("ERROR", "The type image must be RGB or 8 bits gray or 32 bits gray.");
            return null;
        }
        if (imp.getStackSize() > 1) {
            if (imp.getType() == ImagePlus.GRAY8) {
                return new hips.images.gray8.ImageGray8(imp);
            } else if (imp.getType() == ImagePlus.GRAY32) {
                return new hips.images.gray32.ImageGray32(imp);
            }
            IJ.error("ERROR", "The stack type must be 8 bits gray or 32 bits gray.");
            return null;
        }
        IJ.error("ERROR", "Unknown error.");
        return null;
    }

    public void close() {
        if (youngest != null) {
            if (youngest.getImage().getImagePlus() != null) {
                youngest.getImage().getImagePlus().close();
            }
        }
        if (oldest != null) {
            if (oldest.getImage().getImagePlus() != null) {
                oldest.getImage().getImagePlus().close();
            }
        }
        if (output != null) {
            if (output.getImagePlus() != null) {
                output.getImagePlus().close();
            }
        }
    }

    public void mouseDragged(MouseEvent e) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public void mouseMoved(MouseEvent e) {
        int x = output.getImagePlus().getCanvas().offScreenX(e.getX());
        int y = output.getImagePlus().getCanvas().offScreenY(e.getY());
        int pos = output.getWidth() * y + x;
        Region r1 = youngest.getRegionByPixel(pos);
        Region r2 = oldest.getRegionByPixel(pos);

        Region c = new Region(pixels, references[lbl[pos]], references[lbl[pos] + 1] - references[lbl[pos]], 0);
        if (common == null) {
            common = c;
            youngest.getImage().paintRegionAndBackup(r1, youngest.getImage().newPixelValue(255));
            oldest.getImage().paintRegionAndBackup(r2, oldest.getImage().newPixelValue(255));
            youngest.getImage().paintRegion(common, youngest.getImage().newPixelValue(0));
            oldest.getImage().paintRegion(common, oldest.getImage().newPixelValue(0));
        } else if (common != c) {
            common = c;
            youngest.getImage().restoreBackup();
            oldest.getImage().restoreBackup();
            youngest.getImage().paintRegionAndBackup(r1, youngest.getImage().newPixelValue(255));
            oldest.getImage().paintRegionAndBackup(r2, oldest.getImage().newPixelValue(255));
            youngest.getImage().paintRegion(common, youngest.getImage().newPixelValue(0));
            oldest.getImage().paintRegion(common, oldest.getImage().newPixelValue(0));
        }
    }
}
